﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using UtilZ.Dotnet.Ex.DataStruct;

namespace UtilZ.Dotnet.Ex.Cache
{
    /// <summary>
    /// 原始缓存类
    /// </summary>
    public sealed class ObjectCache : IDisposable
    {
        /// <summary>
        /// 缓存项存放字典[key:key;value:缓存项]
        /// </summary>
        private readonly ConcurrentDictionary<object, CacheItem> _cacheDic = new ConcurrentDictionary<object, CacheItem>();

        ///// <summary>
        ///// 过期检查间隔
        ///// </summary>
        //private readonly int _checkIntervalMillisecondsTimeout;

        ///// <summary>
        ///// 缓存项过期检查线程
        ///// </summary>
        //private readonly ThreadEx _expirationChaeckThread;

        private readonly ITimingTask _expirationChaeckTimingTask;

        /// <summary>
        /// 缓存过期检查状态
        /// </summary>
        private ObjectCacheExpirationCheckStatus _expirationChaeckStatus = ObjectCacheExpirationCheckStatus.Stoped;

        /// <summary>
        /// Disposable标识[true:已释放;false:未释放]
        /// </summary>
        private bool _disposed = false;



        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="checkIntervalMillisecondsTimeout">过期检查间隔,单位:毫秒,默认值为1000毫秒</param>
        public ObjectCache(int checkIntervalMillisecondsTimeout = 1000)
        {
            if (checkIntervalMillisecondsTimeout <= 0)
            {
                throw new ArgumentOutOfRangeException("过期检查间隔值不能小于等于0");
            }

            //this._checkIntervalMillisecondsTimeout = checkIntervalMillisecondsTimeout;
            //this._expirationChaeckThread = new ThreadEx(this.ExpirationChaeckThreadMethod, "缓存项过期检查线程", true);

            this._expirationChaeckTimingTask = new CallbackExcuteTimingTask(this.TimingTaskCallback, TimeSpan.FromMilliseconds(checkIntervalMillisecondsTimeout))
            {
                Tag = this
            };
        }

        private void TimingTaskCallback(CallbackExcuteTimingTask timingTask)
        {
            try
            {
                CacheItem[] cacheItemArr = this._cacheDic.Values.ToArray();
                foreach (var cacheItem in cacheItemArr)
                {
                    if (cacheItem.Expiration())
                    {
                        this.ExpirationRemove(cacheItem.Key);
                    }
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
                Console.WriteLine(ex.ToString());
            }
        }


        /// <summary>
        /// 启动缓存过期检查
        /// </summary>
        private void StartExpirationCheck()
        {
            if (this._expirationChaeckStatus == ObjectCacheExpirationCheckStatus.Runing)
            {
                return;
            }

            lock (this)
            {
                if (this._expirationChaeckStatus == ObjectCacheExpirationCheckStatus.Runing)
                {
                    return;
                }

                TimingTaskManager.Default.AddTask(this._expirationChaeckTimingTask);
                this._expirationChaeckStatus = ObjectCacheExpirationCheckStatus.Runing;
            }
        }

        /// <summary>
        /// 如果缓存项数为0,则停止缓存过期检查
        /// </summary>
        private void StopExpirationCheck()
        {
            if (this._expirationChaeckStatus != ObjectCacheExpirationCheckStatus.Runing)
            {
                return;
            }

            lock (this)
            {
                if (this._expirationChaeckStatus == ObjectCacheExpirationCheckStatus.Runing)
                {
                    TimingTaskManager.Default.Remove(this._expirationChaeckTimingTask);
                    this._expirationChaeckStatus = ObjectCacheExpirationCheckStatus.Stoped;
                }
            }
        }


        /*
      /// <summary>
      /// 启动缓存过期检查线程
      /// </summary>
      private void StartExpirationChaeckThread()
      {
          this._expirationChaeckThread.Start();
      }

      /// <summary>
      /// 如果缓存项数为0,则停止缓存过期检查线程
      /// </summary>
      private void StopExpirationChaeckThread()
      {
          if (this._cacheDic.Count == 0)
          {
              this._expirationChaeckThread.Stop();
          }
      }

      private void ExpirationChaeckThreadMethod(ThreadExPara para)
      {
          try
          {
              CacheItem[] cacheItemArr;
              while (!para.Token.IsCancellationRequested)
              {
                  try
                  {
                      para.Token.WaitHandle.WaitOne(this._checkIntervalMillisecondsTimeout, true);
                  }
                  catch (ObjectDisposedException)
                  {
                      break;
                  }
                  catch (AbandonedMutexException)
                  {
                      break;
                  }
                  catch (InvalidOperationException)
                  {
                      break;
                  }

                  try
                  {
                      cacheItemArr = this._cacheDic.Values.ToArray();
                      foreach (var cacheItem in cacheItemArr)
                      {
                          if (cacheItem.Expiration())
                          {
                              this.ExpirationRemove(cacheItem.Key);
                          }
                      }

                      this.StopExpirationChaeckThread();
                  }
                  catch (Exception exi)
                  {
                      ZLoger.Error(exi);
                  }
              }
          }
          catch (Exception ex)
          {
              ZLoger.Error(ex);
          }
      }
      */





        /// <summary>
        /// 返回移除结果[true:移除成功]
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        private bool ExpirationRemove(object key)
        {
            lock (this._cacheDic)
            {
                CacheItem valueItem, tmpItem;
                if (this._cacheDic.TryGetValue(key, out valueItem))
                {
                    if (valueItem.Expiration())
                    {
                        this._cacheDic.TryRemove(key, out tmpItem);
                        valueItem.CallRemovedCallback(this, CacheEntryRemovedReason.Expired);

                        //如果没有缓存项,则停止过期检测
                        if (this._cacheDic.Count == 0)
                        {
                            this.StopExpirationCheck();
                        }

                        return true;
                    }
                }

                return false;
            }
        }








        /// <summary>
        /// 获取数据
        /// </summary>
        /// <param name="key">key</param>
        /// <returns>缓存项,获取成功返回缓存项,值过期或key不存在返回null</returns>
        public object Get(object key)
        {
            if (key == null)
            {
                throw new CacheKeyNullException();
            }

            CacheItem value;
            while (this._cacheDic.TryGetValue(key, out value))
            {
                if (value.Expiration())
                {
                    if (this.ExpirationRemove(key))
                    {
                        return null;
                    }
                    else
                    {
                        continue;
                    }
                }

                value.LastGetTime = DateTimeOffset.Now;
                return value.Value;
            }

            return null;
        }

        /// <summary>
        /// 获取缓存key集合
        /// </summary>
        public ICollection<object> Keys
        {
            get { return this._cacheDic.Keys; }
        }

        /// <summary>
        /// 获取缓存值集合
        /// </summary>
        public ICollection<CacheItem> Values
        {
            get { return this._cacheDic.Values; }
        }

        /// <summary>
        /// 通过where条件筛选符合条件的项集合
        /// </summary>
        /// <param name="predicate">筛选条件</param>
        /// <returns>符合条件的项集合</returns>
        public T[] Where<T>(Func<object, object, bool> predicate)
        {
            if (predicate == null)
            {
                return this._cacheDic.Select(t =>
                {
                    return (T)t.Value.Value;
                }).ToArray();
            }
            else
            {
                return this._cacheDic.Where((kv) =>
                {
                    return predicate(kv.Key, kv.Value.Value);
                }).Select(t =>
                {
                    return (T)t.Value.Value;
                }).ToArray();
            }
        }


        /// <summary>
        /// 是否存在key值的数据[存在返回true;不存在返回false]
        /// </summary>
        /// <param name="key">key</param>
        /// <returns>存在返回true;不存在返回false</returns>
        public bool Exist(object key)
        {
            if (key == null)
            {
                throw new CacheKeyNullException();
            }

            return this._cacheDic.ContainsKey(key);
        }

        /// <summary>
        /// 移除一个缓存项
        /// </summary>
        /// <param name="key">key</param>
        /// <returns>被移除的缓存项</returns>
        public object Remove(object key)
        {
            if (key == null)
            {
                throw new CacheKeyNullException();
            }

            CacheItem value;
            if (this._cacheDic.TryRemove(key, out value))
            {
                value.CallRemovedCallback(this, CacheEntryRemovedReason.Removed);
                return value.Value;
            }

            return null;
        }

        /// <summary>
        /// 清空缓存
        /// </summary>
        public void Clear()
        {
            var values = this._cacheDic.Values.ToArray();
            this._cacheDic.Clear();
            foreach (var value in values)
            {
                value.CallRemovedCallback(this, CacheEntryRemovedReason.Removed);
            }
            this.StopExpirationCheck();
        }






        #region Add
        /// <summary>
        /// 添加缓存项
        /// </summary>
        /// <param name="key">key</param>
        /// <param name="value">缓存项</param>
        public void Add(object key, object value)
        {
            this.PrimitiveAdd(key, value, null, null, null, null);
        }

        /// <summary>
        /// 添加缓存项
        /// </summary>
        /// <param name="key">key</param>
        /// <param name="value">缓存项</param>
        /// <param name="expirationMilliseconds">缓存项绝对有效时间,单位/毫秒</param>
        /// <param name="removedCallback">移除回调</param>
        public void Add(object key, object value, long expirationMilliseconds, CacheEntryRemovedCallback removedCallback = null)
        {
            this.PrimitiveAdd(key, value, DateTimeOffset.Now.AddMilliseconds(expirationMilliseconds), null, null, removedCallback);
        }

        /// <summary>
        /// 添加缓存项
        /// </summary>
        /// <param name="key">key</param>
        /// <param name="value">缓存项</param>
        /// <param name="slidingExpiration">缓存项滑动有效时间</param>
        /// <param name="removedCallback">移除回调</param>
        public void Add(object key, object value, TimeSpan slidingExpiration, CacheEntryRemovedCallback removedCallback = null)
        {
            this.PrimitiveAdd(key, value, DateTimeOffset.Now.Add(slidingExpiration), null, null, removedCallback);
        }

        /// <summary>
        /// 添加缓存项
        /// </summary>
        /// <param name="key">key</param>
        /// <param name="value">缓存项</param>
        /// <param name="customerExpiration">缓存项自定义过期验证回调</param>
        /// <param name="removedCallback">移除回调</param>
        public void Add(object key, object value, Func<CacheItem, bool> customerExpiration, CacheEntryRemovedCallback removedCallback = null)
        {
            this.PrimitiveAdd(key, value, null, null, customerExpiration, removedCallback);
        }



        /// <summary>
        /// 设置缓存项-滑动访问过期
        /// </summary>
        /// <param name="key">key</param>
        /// <param name="value">缓存项</param>
        /// <param name="expirationMilliseconds">缓存项有效时间,单位/毫秒</param>
        /// <param name="removedCallback">移除回调</param>
        public void AddSlide(object key, object value, long expirationMilliseconds, CacheEntryRemovedCallback removedCallback)
        {
            this.PrimitiveAdd(key, value, null, TimeSpan.FromMilliseconds(expirationMilliseconds), null, removedCallback);
        }

        /// <summary>
        /// 设置缓存项-滑动访问过期
        /// </summary>
        /// <param name="key">key</param>
        /// <param name="value">缓存项</param>
        /// <param name="slidingExpiration">缓存项滑动有效时间</param>
        /// <param name="removedCallback">移除回调</param>
        public void AddSlide(object key, object value, TimeSpan slidingExpiration, CacheEntryRemovedCallback removedCallback)
        {
            this.PrimitiveAdd(key, value, null, slidingExpiration, null, removedCallback);
        }



        /// <summary>
        /// 添加缓存项
        /// </summary>
        /// <param name="cacheItem">缓存项</param>
        public void Add(CacheItem cacheItem)
        {
            if (cacheItem == null)
            {
                return;
            }

            if (cacheItem.Key == null)
            {
                throw new CacheKeyNullException();
            }

            lock (this._cacheDic)
            {
                if (this._cacheDic.ContainsKey(cacheItem.Key))
                {
                    throw new CacheKeyExistException(cacheItem.Key);
                }

                this.PrimitiveAdd(cacheItem);
            }
        }

        private void PrimitiveAdd(object key, object value, DateTimeOffset? absoluteExpiration, TimeSpan? slidingExpiration, Func<CacheItem, bool> customerExpiration, CacheEntryRemovedCallback removedCallback)
        {
            if (key == null)
            {
                throw new CacheKeyNullException();
            }

            lock (this._cacheDic)
            {
                if (this._cacheDic.ContainsKey(key))
                {
                    throw new CacheKeyExistException(key);
                }

                CacheItem cacheItem = new CacheItem(key, value);
                cacheItem.AbsoluteExpiration = absoluteExpiration;
                cacheItem.SlidingExpiration = slidingExpiration;
                cacheItem.CustomerExpiration = customerExpiration;
                cacheItem.RemovedCallback = removedCallback;
                cacheItem.InnerRemovedCallback = true;
                this.PrimitiveAdd(cacheItem);
            }
        }

        private void PrimitiveAdd(CacheItem cacheItem)
        {
            if (this._disposed)
            {
                throw new ObjectDisposedException(this.ToString());
            }

            cacheItem.AddTime = DateTimeOffset.Now;
            if (this._cacheDic.TryAdd(cacheItem.Key, cacheItem))
            {
                this.StartExpirationCheck();
            }
            else
            {
                throw new CacheAddException(cacheItem.Key);
            }
        }
        #endregion








        /// <summary>
        /// 存储数据
        /// </summary>
        /// <param name="key">key</param>
        /// <param name="value">缓存项</param>
        public void Set(object key, object value)
        {
            this.PrimitiveSet(key, value, null, null, null, null);
        }

        /// <summary>
        /// 设置缓存项
        /// </summary>
        /// <param name="key">key</param>
        /// <param name="value">缓存项</param>
        /// <param name="expirationMilliseconds">缓存项有效时间,单位/毫秒</param>
        /// <param name="removedCallback">移除回调</param>
        public void Set(object key, object value, long expirationMilliseconds, CacheEntryRemovedCallback removedCallback = null)
        {
            this.PrimitiveSet(key, value, DateTimeOffset.Now.AddMilliseconds(expirationMilliseconds), null, null, removedCallback);
        }

        /// <summary>
        /// 设置缓存项
        /// </summary>
        /// <param name="key">key</param>
        /// <param name="value">缓存项</param>
        /// <param name="expiration">缓存项有效时间</param>
        /// <param name="removedCallback">移除回调</param>
        public void Set(object key, object value, TimeSpan expiration, CacheEntryRemovedCallback removedCallback = null)
        {
            this.PrimitiveSet(key, value, DateTimeOffset.Now.Add(expiration), null, null, removedCallback);
        }

        /// <summary>
        /// 设置缓存项
        /// </summary>
        /// <param name="key">key</param>
        /// <param name="value">缓存项</param>
        /// <param name="customerExpiration">缓存项自定义过期验证回调</param>
        /// <param name="removedCallback">移除回调</param>
        public void Set(object key, object value, Func<CacheItem, bool> customerExpiration, CacheEntryRemovedCallback removedCallback = null)
        {
            this.PrimitiveSet(key, value, null, null, customerExpiration, removedCallback);
        }


        /// <summary>
        /// 设置缓存项-滑动访问过期
        /// </summary>
        /// <param name="key">key</param>
        /// <param name="value">缓存项</param>
        /// <param name="expirationMilliseconds">缓存项有效时间,单位/毫秒</param>
        /// <param name="removedCallback">移除回调</param>
        public void SetSlide(object key, object value, long expirationMilliseconds, CacheEntryRemovedCallback removedCallback)
        {
            this.PrimitiveSet(key, value, null, TimeSpan.FromMilliseconds(expirationMilliseconds), null, removedCallback);
        }

        /// <summary>
        /// 设置缓存项-滑动访问过期
        /// </summary>
        /// <param name="key">key</param>
        /// <param name="value">缓存项</param>
        /// <param name="slidingExpiration">缓存项滑动有效时间</param>
        /// <param name="removedCallback">移除回调</param>
        public void SetSlide(object key, object value, TimeSpan slidingExpiration, CacheEntryRemovedCallback removedCallback)
        {
            this.PrimitiveSet(key, value, null, slidingExpiration, null, removedCallback);
        }


        /// <summary>
        /// 设置缓存项
        /// </summary>
        /// <param name="cacheItem">缓存项</param>
        public void Set(CacheItem cacheItem)
        {
            if (cacheItem == null)
            {
                return;
            }

            if (cacheItem.Key == null)
            {
                throw new CacheKeyNullException();
            }

            this.PrimitiveSet(cacheItem);
        }

        private void PrimitiveSet(object key, object value, DateTimeOffset? absoluteExpiration, TimeSpan? slidingExpiration, Func<CacheItem, bool> customerExpiration, CacheEntryRemovedCallback removedCallback)
        {
            if (key == null)
            {
                throw new CacheKeyNullException();
            }

            CacheItem cacheItem;
            if (this._cacheDic.TryGetValue(key, out cacheItem))
            {
                if (!this.IsIdenticalObject(cacheItem, value))
                {
                    cacheItem = null;
                }
            }

            if (cacheItem == null)
            {
                cacheItem = new CacheItem(key, value);
            }

            cacheItem.AbsoluteExpiration = absoluteExpiration;
            cacheItem.SlidingExpiration = slidingExpiration;
            cacheItem.CustomerExpiration = customerExpiration;
            cacheItem.RemovedCallback = removedCallback;
            cacheItem.InnerRemovedCallback = true;

            this.PrimitiveSet(cacheItem);
        }


        private void PrimitiveSet(CacheItem cacheItem)
        {
            if (this._disposed)
            {
                throw new ObjectDisposedException(this.ToString());
            }

            lock (this._cacheDic)
            {
                cacheItem.AddTime = DateTimeOffset.Now;
                CacheItem oldCacheItem = null;
                this._cacheDic.AddOrUpdate(cacheItem.Key, cacheItem, (k, old) =>
                {
                    if (!this.IsIdenticalObject(old, cacheItem.Value))
                    {
                        oldCacheItem = old;
                    }

                    return cacheItem;
                });
                this.StartExpirationCheck();

                if (oldCacheItem != null)
                {
                    //触发移除回调
                    oldCacheItem.CallRemovedCallback(this, CacheEntryRemovedReason.Replace, cacheItem);
                }
            }
        }

        /// <summary>
        /// 判断cacheItem.Value与value是否是相同项,相同返回true,不同返回false
        /// </summary>
        /// <param name="cacheItem">CacheItem</param>
        /// <param name="value">object</param>
        /// <returns>相同返回true,不同返回false</returns>
        private bool IsIdenticalObject(CacheItem cacheItem, object value)
        {
            if (cacheItem == null)
            {
                return false;
            }

            if (cacheItem.Value == value)
            {
                return true;
            }

            if (!object.Equals(cacheItem.Value, value))
            {
                return true;
            }

            return false;
        }









        /// <summary>
        /// Dispose
        /// </summary>
        public void Dispose()
        {
            try
            {
                if (this._disposed)
                {
                    return;
                }
                this._disposed = true;

                this.Clear();
                //this._expirationChaeckThread.Dispose();
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
                Console.WriteLine(ex.ToString());
            }
        }

    }


}
